package com.wbs.rest.model;


import com.fasterxml.jackson.databind.ObjectMapper;
import com.wbs.rest.config.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpMethod;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.util.StringUtils;
import org.springframework.web.util.UriTemplate;
import java.util.*;
import java.util.stream.Collectors;

public class JdbcSqlModelResolver implements SqlModelResolver {

    Logger log = LoggerFactory.getLogger(JdbcSqlModelResolver.class);

    private static final String DEFAULT_PARAM = null;
    private static final ObjectMapper JSON_MAPPER = new ObjectMapper();



    JdbcTemplate jdbcTemplate;

    SqlConfig sqlConfig;


    public JdbcSqlModelResolver(JdbcTemplate jdbcTemplate, SqlConfig sqlConfig) {
        this.jdbcTemplate = jdbcTemplate;
        this.sqlConfig = sqlConfig;
    }

    @Override
    public List<Map> resolveParameter(String content, Map requestMap) {
        List<Map> parameterList = new ArrayList<>();

        try{
            if(StringUtils.isEmpty(content)){
                parameterList.add(requestMap);
            }
            else if (content.trim().startsWith("[{")) {
                parameterList = JSON_MAPPER.readValue(content, List.class);
            }
            else if (content.trim().startsWith("{")) {
                parameterList.add(JSON_MAPPER.readValue(content, Map.class));
            }
        }catch (Exception e){
            e.printStackTrace();
        }


        return parameterList;
    }

    @Override
    public SqlModelEnum resolveSqlModelEnum(List<Map> parameterList, String httpMethod) {

        if(parameterList.size() > 1){
            return SqlModelEnum.BATCH;
        }

        else if(parameterList.size() == 1){
            Map parameterMap = parameterList.get(0);

            if (parameterMap.containsKey(SqlConst.FIELD_MULTI_MODELS)) {
                return SqlModelEnum.MULTI;
            }
            else if (parameterMap.containsKey(SqlConst.FIELD_PAGE) && parameterMap.containsKey(SqlConst.FIELD_SIZE)) {
                return SqlModelEnum.PAGE;
            }
        }


        return SqlModelEnum.DEFAULT;
    }


    @Override
    public SqlModel resolveModel(String uri, List<Map> parameterList, SqlModelEnum modelEnum) {

        if(modelEnum == SqlModelEnum.BATCH){
            return resolveBatchModel(uri, parameterList);
        }

        else if(modelEnum == SqlModelEnum.PAGE){
            return resolvePageModel(uri, parameterList.get(0));
        }

        else if(modelEnum == SqlModelEnum.MULTI){
            return resolveMultiModel(uri, parameterList.get(0));
        }

        SqlModel sqlModel = new SqlModel();

        if (resolveCommonProperties(uri, sqlModel)) {
            sqlModel.setArgs(sortArgs(sqlModel, parameterList.size() > 0 ? parameterList.get(0) : new HashMap()));
        }

        return sqlModel;
    }

    public SqlModel resolveModel(String baseUri, String mappingUri, Map parameterMap) {
        SqlModel sqlModel = new SqlModel();

        if (resolveCommonProperties(baseUri, mappingUri, sqlModel)) {
            sqlModel.setArgs(sortArgs(sqlModel, parameterMap));
        }

        return sqlModel;
    }

    @Override
    public SqlModel resolvePageModel(String uri, Map parameterMap) {
        PageQuerySqlModel sqlModel = new PageQuerySqlModel();
        sqlModel.setPage(Integer.valueOf(parameterMap.remove(SqlConst.FIELD_PAGE).toString()));
        sqlModel.setSize(Integer.valueOf(parameterMap.remove(SqlConst.FIELD_SIZE).toString()));

        if (resolveCommonProperties(uri, sqlModel)) {
            sqlModel.setArgs(sortArgs(sqlModel, parameterMap));
        }

        return sqlModel;
    }



    @Override
    public SqlModel resolveBatchModel(String uri, List<Map> parameterList) {
        BatchSqlModel sqlModel = new BatchSqlModel();

        if (resolveCommonProperties(uri, sqlModel)) {
            sqlModel.setArgsList(sortArgs(sqlModel, parameterList));
        }

        return sqlModel;
    }



    @Override
    public SqlModel resolveMultiModel(String uri, Map parameterMap) {
        MultiSqlModel sqlModel = new MultiSqlModel();

        resolveCommonProperties(uri, sqlModel);

        sqlModel.setSqlModels(resolveMultiModel(sqlModel.getBaseUri(), parameterMap,false));

        return sqlModel;
    }

    private List<SqlModel> resolveMultiModel(String baseUri, Map parameterMap, boolean handleField) {

        List<SqlModel> sqlModels = new ArrayList<>();

        try {

            List<Map> listParams = (List<Map>) parameterMap.get(SqlConst.FIELD_MULTI_MODELS);

            for (Map map : listParams) {
                String mappingUri = (String) map.remove(SqlConst.FIELD_URI);
                sqlModels.add(resolveModel(baseUri, mappingUri, map));
            }

            if(handleField){
                for (SqlModel model : sqlModels) {
                    if(model.getArgNames() != null){

                        List<String> keyNames = model.getArgNames().stream().filter((tmp) -> tmp.startsWith(SqlConst.KEY_INCREASE_PREFIX)).collect(Collectors.toList());
                        for (String keyName : keyNames) {

                            if (SqlConst.KEY_UUID.equals(keyName)) {
                                model.setSqlKeyEnum(SqlKeyEnum.UUID);
                            } else {
                                String modelId = keyName.replace(SqlConst.KEY_INCREASE_PREFIX, "");
                                Optional<SqlModel> keyModel = sqlModels.stream().filter((tmp) -> modelId.equals(tmp.getId().toString())).findFirst();
                                if (keyModel.isPresent()) {
                                    keyModel.get().setSqlKeyEnum(SqlKeyEnum.INCREASE);
                                }
                            }
                        }

                    }
                }
            }


        } catch (Exception e) {
            e.printStackTrace();
        }

        return sqlModels;
    }





    @Override
    public void loadMapping() {
        SqlMappingCacher.reset(jdbcTemplate.queryForList(sqlConfig.getQuerySql()));
        log.info("finish loading sql-rest mapping");
    }

    @Override
    public List<SqlModel> queryAllModels() {
        List<Map<String, Object>> data = jdbcTemplate.queryForList(sqlConfig.getQuerySql());
        List<SqlModel> sqlModels = new ArrayList<>();
        for (Map map : data) {
            SqlModel sqlModel = new SqlModel();
            mapToModel(map, sqlModel);
            sqlModels.add(sqlModel);
        }

        return sqlModels;
    }

    @Override
    public boolean resolveCommonProperties(String uri, SqlModel sqlModel) {
        UriTemplate uriTemplate = new UriTemplate(SqlRestAutoBeanConfig.BASE_URI + SqlRestAutoBeanConfig.MAPPING_URI);
        Map params = uriTemplate.match(uri);
        String baseUri = (String) params.get(SqlRestAutoBeanConfig.BASE_URI.replaceAll("\\/|\\{|\\}", ""));
        String mappingUri = (String) params.get(SqlRestAutoBeanConfig.MAPPING_URI.replaceAll("\\/|\\{|\\}", ""));

        return resolveCommonProperties(baseUri, mappingUri, sqlModel);
    }

    private boolean resolveCommonProperties(String baseUri, String mappingUri, SqlModel sqlModel) {
        log.info("mapping_uri : {}", mappingUri);

        if (mappingUri == null) {
            return false;
        }

        sqlModel.setBaseUri(baseUri);

        Map map = SqlMappingCacher.getMappingByUri(mappingUri);
        if(map != null){
            mapToModel(map, sqlModel);
            return true;
        }

        return false;
    }

    private void mapToModel(Map map, SqlModel sqlModel) {
        Object id = map.get("id");
        String sql = (String) map.get("sql");
        String sqlType = (String) map.get("sql_type");
        String names = (String) map.get("arg_names");
        String methods = (String) map.get("http_methods");
        String mappingUri = (String) map.get("mapping_uri");
        String title = (String) map.get("title");
        String description = (String) map.get("description");
        sqlModel.setBaseUri(sqlConfig.getBaseUri());
        sqlModel.setMappingUri(mappingUri);
        sqlModel.setId(id);
        sqlModel.setSql(sql);
        sqlModel.setSqlType(sqlType);
        sqlModel.setTitle(title);
        sqlModel.setDescription(description);

        if (!StringUtils.isEmpty(names)) {
            sqlModel.setArgNames(Arrays.asList(names.split(",")));
        }

        if (!StringUtils.isEmpty(methods)) {
            for (String type : methods.split(",")) {
                sqlModel.getHttpMethods().add(HttpMethod.resolve(type.toUpperCase()));
            }
        }
    }


    private List<Object[]> sortArgs(SqlModel sqlModel, List<Map> paramsList) {
        List<Object[]> argsList = new ArrayList<>();
        for (Map params : paramsList) {
            argsList.add(sortArgs(sqlModel, params));
        }
        return argsList;
    }

    private Object[] sortArgs(SqlModel sqlModel, Map params) {
        List args = new ArrayList();

        if (sqlModel.getArgNames() == null) {
            return args.toArray();
        }

        for (String name : sqlModel.getArgNames()) {
            if (params.containsKey(name)) {
                args.add(params.get(name));
            } else {
                args.add(DEFAULT_PARAM);
            }
        }
        return args.toArray();
    }

}
